using System;
using System.Text;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace AVICapWrapper
{
    /// <summary>
    /// AVI capturing control
    /// </summary>
    public class AVICapture : Panel
    {
        public const uint WS_VISIBLE = 0x10000000;
        public const uint WS_CHILD = 0x40000000;
        public const uint WM_CAP_DRIVER_CONNECT = 0x40A;
        public const uint WM_CAP_DRIVER_DISCONNECT = 0x40B;
        public const uint WM_CAP_SET_PREVIEWRATE = 0x434;
        public const uint WM_CAP_SET_PREVIEW = 0x432;
        public const uint WM_CAP_SET_CALLBACK_FRAME = 0x405;
        public const uint WM_CAP_SET_VIDEOFORMAT = 0x42D;

        private int m_DriverIndex;
        private int m_FrameDelay;
        private IntPtr m_HandleCaptureWindow;
        private BITMAPINFO m_VideoFormat;

        // Events
        public event FrameCallbackEvent OnFrameReceived = null;

        // Delegate for Frame Callback
        public delegate void FrameCallbackEvent(IntPtr handleCapture, IntPtr videoHeader);
        public FrameCallbackEvent m_FrameCallback = null;
        public static FrameCallbackEvent m_FrameCallback2 = null;

        /// <summary>
        /// Caps the create capture window W.
        /// </summary>
        /// <param name="windowTitle">The window title.</param>
        /// <param name="style">The style.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="parentWindowHandle">The parent window handle.</param>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        [DllImport("avicap32.dll", SetLastError = true)]
        public static extern IntPtr capCreateCaptureWindowW(string windowTitle, uint style, int x, int y, int width, int height, IntPtr parentWindowHandle, int id);

        /// <summary>
        /// Sends the message.
        /// </summary>
        /// <param name="handleWindow">The handle window.</param>
        /// <param name="message">The message.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="lParam">The l param.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        public static extern int SendMessage(IntPtr handleWindow, uint message, ushort wParam, ushort lParam);

        /// <summary>
        /// Sends the message.
        /// </summary>
        /// <param name="handleWindow">The handle window.</param>
        /// <param name="message">The message.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="callback">The callback.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        public static extern int SendMessage(IntPtr handleWindow, uint message, ushort wParam, FrameCallbackEvent callback);

        /// <summary>
        /// Sends the message.
        /// </summary>
        /// <param name="handleWindow">The handle window.</param>
        /// <param name="message">The message.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="videoformat">The videoformat.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        public static extern int SendMessage(IntPtr handleWindow, uint message, int wParam, ref BITMAPINFO videoformat);

        [DllImport("avicap32.dll", SetLastError = true)]
        public static extern bool capGetDriverDescription(int index, StringBuilder driverName, int driverNameLen, StringBuilder driverDescr, int driverDescrLen);

        /*
        ** Constructors
        */
        /// <summary>
        /// Initializes a new instance of the <see cref="AVICapture"/> class.
        /// </summary>
        public AVICapture()
            : this(0, 10, new Size(320, 240))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AVICapture"/> class.
        /// </summary>
        /// <param name="driverIndex">Index of the driver.</param>
        /// <param name="frameRate">The frame rate.</param>
        /// <param name="videoSize">Size of the video.</param>
        public AVICapture(int driverIndex, int frameRate, Size videoSize)
        {
            SetVideoFormat(videoSize);
            SetFrameRate(frameRate);
            m_DriverIndex = driverIndex;
        }

        /// <summary>
        /// Sets the frame rate.
        /// </summary>
        /// <param name="frameRate">The frame rate.</param>
        public void SetFrameRate(int frameRate)
        {
            m_FrameDelay = (int)1000 / frameRate;
            if (m_HandleCaptureWindow != IntPtr.Zero)
            {
                SendMessage(m_HandleCaptureWindow, WM_CAP_SET_PREVIEWRATE, (ushort)m_FrameDelay, 0);
            }
        }

        /// <summary>
        /// Sets the video format.
        /// </summary>
        /// <param name="videoSize">Size of the video.</param>
        public void SetVideoFormat(Size videoSize)
        {
            this.Size = videoSize;
            m_VideoFormat = new BITMAPINFO();
            m_VideoFormat.bmiHeader.biSize = Marshal.SizeOf(m_VideoFormat.bmiHeader);
            m_VideoFormat.bmiHeader.biWidth = videoSize.Width;
            m_VideoFormat.bmiHeader.biHeight = videoSize.Height;
            m_VideoFormat.bmiHeader.biPlanes = 1;
            m_VideoFormat.bmiHeader.biBitCount = 24;
        }

        /// <summary>
        /// Gets the installed drivers.
        /// </summary>
        /// <returns></returns>
        public static List<AVIDriver> GetInstalledDrivers()
        {
            List<AVIDriver> drivers = new List<AVIDriver>();

            for (int index = 0; index < 10; index++)
            {
                StringBuilder driverName = new StringBuilder(100);
                StringBuilder driverDescr = new StringBuilder(100);
                if (capGetDriverDescription(index, driverName, driverName.Capacity, driverDescr, driverDescr.Capacity))
                {
                    AVIDriver driver = new AVIDriver();
                    driver.DriverIndex = index;
                    driver.DriverName = driverName.ToString();
                    driver.DriverDescription = driverDescr.ToString();
                    drivers.Add(driver);
                }
            }

            return drivers;
        }

        /// <summary>
        /// Starts the streaming.
        /// </summary>
        public void StartStreaming()
        {
            Connect(); // Connecting the Video for Windows Driver to our Window

            SendMessage(m_HandleCaptureWindow, WM_CAP_SET_PREVIEWRATE, (ushort)m_FrameDelay, 0);
            SendMessage(m_HandleCaptureWindow, WM_CAP_SET_PREVIEW, 1, 0);

            // Setting up Video Format
            AVICapture.SendMessage(m_HandleCaptureWindow, AVICapture.WM_CAP_SET_VIDEOFORMAT, Marshal.SizeOf(m_VideoFormat), ref m_VideoFormat);

            // Setting up Event Handlers
            m_FrameCallback2 = new FrameCallbackEvent(this.FrameCallbackHandler);
            SendMessage(m_HandleCaptureWindow, WM_CAP_SET_CALLBACK_FRAME, 0, m_FrameCallback2);
        }

        /// <summary>
        /// Stops the streaming.
        /// </summary>
        public void StopStreaming()
        {
            Disconnect();
        }

        /// <summary>
        /// Disconnects this instance.
        /// </summary>
        private void Disconnect()
        {
            if (m_HandleCaptureWindow != IntPtr.Zero)
            {
                SendMessage(m_HandleCaptureWindow, WM_CAP_DRIVER_DISCONNECT, 0, 0);
            }
        }

        /// <summary>
        /// Connects this instance.
        /// </summary>
        private void Connect()
        {
            if ((m_HandleCaptureWindow = capCreateCaptureWindowW("Capture", (AVICapture.WS_CHILD | AVICapture.WS_VISIBLE), 0, 0, m_VideoFormat.bmiHeader.biWidth, m_VideoFormat.bmiHeader.biHeight, this.Handle, 0)) == IntPtr.Zero)
            {
                throw (new AVIException("Failed to create capture Window"));
            }

            SendMessage(m_HandleCaptureWindow, WM_CAP_DRIVER_CONNECT, (ushort)m_DriverIndex, 0);
        }

        /// <summary>
        /// Gets a bitmap from an unmanaged ptr
        /// </summary>
        /// <param name="videoHeader"></param>
        /// <returns></returns>
        public Bitmap GetBitmapFromUnmanagedPtr(IntPtr videoHeader)
        {
            VIDEOHDR vhdr = new VIDEOHDR();
            vhdr = (VIDEOHDR)Marshal.PtrToStructure(videoHeader, vhdr.GetType());

            byte[] imageData = new byte[vhdr.dwBytesUsed];
            Marshal.Copy(new IntPtr(vhdr.lpData), imageData, 0, imageData.Length);

            System.Drawing.Bitmap bmp = new Bitmap(m_VideoFormat.bmiHeader.biWidth, m_VideoFormat.bmiHeader.biHeight);

            System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, m_VideoFormat.bmiHeader.biWidth, m_VideoFormat.bmiHeader.biHeight), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            int startAddr = bmpData.Scan0.ToInt32();
            for (int row = m_VideoFormat.bmiHeader.biHeight - 1; row >= 0; row--)
            {
                // ** Woot! Dirty trick, may not work on all platforms.... egrath
                Marshal.Copy(imageData, (row * m_VideoFormat.bmiHeader.biWidth * 3), new IntPtr(startAddr), m_VideoFormat.bmiHeader.biWidth * 3);
                startAddr += m_VideoFormat.bmiHeader.biWidth * 3;
            }
            bmp.UnlockBits(bmpData);

            return bmp;
        }

        /// <summary>
        /// Frames the callback handler.
        /// </summary>
        /// <param name="handleCapture">The handle capture.</param>
        /// <param name="videoHeader">The video header.</param>
        private void FrameCallbackHandler(IntPtr handleCapture, IntPtr videoHeader)
        {
            if (OnFrameReceived != null)
            {
                OnFrameReceived(handleCapture, videoHeader);
            }
        }
    }

    /// <summary>
    /// AVI Driver
    /// </summary>
    public struct AVIDriver
    {
        public int DriverIndex;
        public string DriverName;
        public string DriverDescription;
    }

    /// <summary>
    /// VIDEO HDR
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct VIDEOHDR
    {
        [MarshalAs(UnmanagedType.I4)]
        public int lpData;
        [MarshalAs(UnmanagedType.I4)]
        public int dwBufferLength;
        [MarshalAs(UnmanagedType.I4)]
        public int dwBytesUsed;
        [MarshalAs(UnmanagedType.I4)]
        public int dwTimeCaptured;
        [MarshalAs(UnmanagedType.I4)]
        public int dwUser;
        [MarshalAs(UnmanagedType.I4)]
        public int dwFlags;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public int[] dwReserved;
    }

    /// <summary>
    /// BITMAPINFOHEADER
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct BITMAPINFOHEADER
    {
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biSize;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biWidth;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biHeight;
        [MarshalAs(UnmanagedType.I2)]
        public short biPlanes;
        [MarshalAs(UnmanagedType.I2)]
        public short biBitCount;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biCompression;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biSizeImage;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biXPelsPerMeter;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biYPelsPerMeter;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biClrUsed;
        [MarshalAs(UnmanagedType.I4)]
        public Int32 biClrImportant;
    }

    /// <summary>
    /// BITMAPINFO
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct BITMAPINFO
    {
        [MarshalAs(UnmanagedType.Struct, SizeConst = 40)]
        public BITMAPINFOHEADER bmiHeader;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]
        public Int32[] bmiColors;
    }
}
